#include <bolt.h>
#include "netnode.h"
#include "gw.h"
#include "tp.h"
#include "tap.h"
#include "pktio.h"
#include "l2.h"
#include "l3.h"
#include "pkt_pool.h"
#include "pkt_queue.h"
#include "gw_event.h"

int debug_gw = 0;

struct gw gw_pool[GW_MAX];

static TAILQ_HEAD(gw_use, gw) gw_use = TAILQ_HEAD_INITIALIZER(gw_use);


static void free_gw(struct gw *g)
{
  g->use = 0;
}

static struct gw *alloc_gw(int id)
{
  if (!valid_gw_id(id))
    return NULL;
  struct gw *g = &gw_pool[id];
  if (g->use)
    return NULL;
  memset(g, 0, sizeof(struct gw));
  g->id = id;
  g->use = 1;
  return g;
}

struct gw *gw_get(int id)
{
  return (valid_gw_id(id) && gw_pool[id].use) ? &gw_pool[id] : NULL;
}

static struct gw *find_gw(const char *name)
{
  struct gw *lookup;
  TAILQ_FOREACH(lookup, &gw_use, entry) {
    if (strcmp(lookup->name, name) == 0)
      break;
  }
  return lookup;
} 

static void gw_unset_lan(int gw_id)
{
  struct gw * gw = gw_get(gw_id);
  if (!gw)
    return;
  gw->lan = -1;
}

static void gw_unset_wan(int gw_id)
{
  struct gw * gw = gw_get(gw_id);
  if (!gw)
    return;
  gw->wan = -1;
}


static bool remove_gw(int id)
{
  struct gw *g = gw_get(id);
  if (g) {
    TAILQ_REMOVE(&gw_use, g, entry);
    gw_unset_wan(id);
    gw_unset_lan(id);
    free_gw(g);
    return true;
  } else 
    return false;
}

static bool gw_set_lan(int gw_id, int lan)
{
  struct gw * gw = gw_get(gw_id);
  if (!gw) {
    return false;
  }
  if (!valid_netnode_id(lan)){
    return false;
  }
  gw->lan = lan;
  return true;
}


static bool gw_set_wan(int gw_id, int wan)
{
  struct gw * gw = gw_get(gw_id);
  if (!gw) {
    return false;
  }
  if (!valid_netnode_id(wan))
    return false;
  gw->wan = wan;
  return true;
}

static bool create_gw(int id, const char *name)
{
  struct gw *g = find_gw(name);
  if (g)
    return false;
  g = alloc_gw(id);
  if (!g)
    return false;
  strncpy(g->name, name, MAX_NAME_LEN -1);
  g->wan = -1;
  g->lan = -1;
  g->wan_rxq = -1;
  g->wan_txq = -1;

  g->lan_rxq = -1;
  g->lan_txq = -1;
  TAILQ_INSERT_TAIL(&gw_use, g, entry);
  return true;
}


void gw_show(void)
{
  struct gw *lookup;

  printf ("%2s %16s %3s %7s %7s %3s %7s %7s\n", 
    "id", "name", "wan", "wan-rxq", "wan-txq", "lan", "lan-rxq", "lan-txq");

  TAILQ_FOREACH(lookup, &gw_use, entry) {
    printf ("%2d %16s %3d %7d %7d %3d %7d %7d\n",
      lookup->id,lookup->name,
      lookup->wan, lookup->wan_rxq, lookup->wan_txq, 
      lookup->lan, lookup->lan_rxq, lookup->lan_txq);
  }
}

BOLT(gw_release, "gw <id> release")
{
  uint32_t id;
  const char *argz = pop_argz();
  if (!str2u32(argz, &id)) {
    printf("invalid id\n");
    return;
  }
  if (remove_gw((int)id)) 
    printf ("success\n");
  else
    printf ("failure\n");
}

BOLT(gw_create, "gw <id> create <name>")
{
  uint32_t id;
  char name [MAX_NAME_LEN];
  const char *argz = pop_argz();
  if (argz == NULL || argz[0] == 0 || strlen(argz) >= MAX_NAME_LEN){
    printf("invalid name\n");
    return;
  }
  strncpy(name, argz, MAX_NAME_LEN - 1);
  argz = pop_argz();
  if (!str2u32(argz, &id)) {
    printf("invalid id\n");
    return;
  }
  if (create_gw(id, name))
    printf ("success\n");
  else
    printf ("failure\n");
}

BOLT(gw_wan, "gw <id> wan <wan-id>")
{
  uint32_t gw, wan;
  const char *argz = pop_argz();
  if (!str2u32(argz, &wan)) {
    printf("invalid wan\n");
    return;
  }
  argz = pop_argz();
  if (!str2u32(argz, &gw)) {
    printf("invalid gw\n");
    return;
  }
  if (gw_set_wan(gw, wan))
    printf("success\n");
  else
    printf("failure\n");
}

BOLT(gw_lan, "gw <id> lan <lan-id>")
{
  uint32_t gw, lan;
  const char *argz = pop_argz();
  if (!str2u32(argz, &lan)) {
    printf("invalid lan\n");
    return;
  }
  argz = pop_argz();
  if (!str2u32(argz, &gw)) {
    printf("invalid gw\n");
    return;
  }
  if (gw_set_lan(gw, lan))
    printf("success\n");
  else
    printf("failure\n");
}

BOLT(gw_show, "gw show")
{
  gw_show();
}


void init_gateway (void)
{
  LOG ("init_scheduler start\n");
  init_scheduler ();
  LOG ("create_thread start\n");
  scheduler.run_queue[1] = create_thread (tp);
  LOG ("scps_Init start\n");
  (void) scps_Init ();
  LOG ("start_threads start\n");
  start_threads ();
  LOG ("tp start\n");

}

static el_handle_t a_handle = NULL, b_handle = NULL;

void start_gateway(void)
{
  if (divert_gwio != NULL) {
    if (a_handle == NULL && divert_gwio->tap_a_fd > 0) {
      struct el_events a_events = {
        .in_event = tap_a_in_event,
        .out_event = NULL,
      };
      a_handle = el_add(divert_gwio->tap_a_fd, &a_events, NULL);
    }
    if (b_handle == NULL && divert_gwio->tap_b_fd > 0) {
      struct el_events b_events = {
        .in_event = tap_b_in_event,
        .out_event = NULL,
      };
      b_handle = el_add(divert_gwio->tap_b_fd, &b_events, NULL);
    }
  }
}

void stop_gateway(void)
{
  if (a_handle != NULL) 
    el_rm(a_handle);
  a_handle = NULL;
  if (b_handle != NULL)
    el_rm(b_handle);
  b_handle = NULL;
}

void gw_deinit(void)
{
  while (!TAILQ_EMPTY(&gw_use)) {
    struct gw *g = TAILQ_FIRST(&gw_use);
    remove_gw(g->id);
  }
  netnode_deinit();
  pktio_deinit();
  pkt_pool_deinit();
  pkt_queue_deinit();
  l3_deinit();
  l2_deinit();
}

void gw_init(void)
{
  int i;
  for (i = 0; i < GW_MAX; i++) {
    free_gw(&gw_pool[i]);
  }
  netnode_init();
  pktio_init();
  pkt_pool_init();
  pkt_queue_init();
  l2_init();
  l3_init();
}

BOLT(gw_init, "gateway init")
{
  LOG ("init gateway\n");
  init_gateway ();
  LOG ("end init gateway\n");
}

BOLT(gw_start, "gateway start")
{
  LOG ("start init gateway\n");
  start_gateway ();
  LOG ("end init gateway\n");
}

BOLT(gw_stop, "gateway stop")
{
  LOG ("stop init gateway\n");
  stop_gateway ();
  LOG ("end init gateway\n");
}

BOLT(gw_debug, "debug gw on")
{
  debug_gw = 1;
}

BOLT(gw_undebug, "debug gw off")
{
  debug_gw = 0;
}

BOLT_PROFILE(gw, "netnode", gw_profile, desc)
{
  if (debug_gw)
    cmd_load_profile(desc, "debug gw on\n");

  struct gw *lookup;
  TAILQ_FOREACH(lookup, &gw_use, entry) {
    cmd_load_profile(desc, "gw %d create %s", lookup->id, lookup->name);
    if (lookup->wan >= 0)
      cmd_load_profile(desc, "gw %d wan %d", lookup->id, lookup->wan);
    if (lookup->wan_rxq >= 0)
      cmd_load_profile(desc, "gw %d queue %d wan-rx", lookup->id, lookup->wan_rxq);
    if (lookup->wan_txq >= 0)
      cmd_load_profile(desc, "gw %d queue %d wan-tx", lookup->id, lookup->wan_txq);

    if (lookup->lan >= 0)
      cmd_load_profile(desc, "gw %d lan %d", lookup->id, lookup->lan);
    if (lookup->lan_rxq >= 0)
      cmd_load_profile(desc, "gw %d queue %d lan-rx", lookup->id, lookup->lan_rxq);
    if (lookup->lan_txq >= 0)
      cmd_load_profile(desc, "gw %d queue %d lan-tx", lookup->id, lookup->lan_txq);
  }
  cmd_load_profile(desc, "\n");
}




